#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <fcntl.h>

#define LINE_SIZE 1024
#define ARGC_SIZE 32

#define LEFT "["
#define RIGHT "]"
#define LABLE "#"

#define DELIM " \t"
#define EXIT_CODE 134

#define NONE -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2

extern char** environ;

char command_line[LINE_SIZE];
char* argv[ARGC_SIZE];
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];

int quit = 0;
int lastcode = 0;
int rdir = NONE;
char* rdirfilename = NULL;

const char* get_name()
{
    return getenv("USER");
}

const char* get_hostname()
{
    return getenv("HOSTNAME");
}

void get_pwd()
{
    getcwd(pwd, sizeof(pwd));
}

void check_redir(char* cmd)
{
    for (int i = 0; cmd[i]; ++i)
    {
        if (cmd[i] == '>')
        {
            cmd[i] = '\0';
            i += 1;
            if (cmd[i] == '>')
            {
                cmd[i] = '\0';
                i += 1;
                rdir = APPEND_RDIR;
            }
            else
                rdir = OUT_RDIR;
            while (isspace(cmd[i]))
                ++i;
            rdirfilename = cmd + i;
            break;
        }
        else if (cmd[i] == '<')
        {
            cmd[i] = '\0';
            i += 1;
            while (isspace(cmd[i]))
                ++i;
            rdir = IN_RDIR;
            rdirfilename = cmd + i;
            break;
        }
    }
}

void interact(char* cline, int clinesize)
{
    get_pwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE" ", get_name(), get_hostname(), pwd);
    fgets(cline, clinesize - 1, stdin);
    cline[strlen(cline) - 1] = '\0';

    check_redir(cline);
}

int split_string(char* command_line, char* argv[])
{
    int i = 0;
    argv[i++] = strtok(command_line, DELIM);
    while (argv[i - 1] != NULL && (argv[i++] = strtok(NULL, DELIM)));
    return i - 1;
}

void normal_execute(char* argv[])
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        return ;
    }
    else if (id == 0)
    {
        int fd = 0;
        if (rdir == IN_RDIR)
        {
            fd = open(rdirfilename, O_RDONLY);
            dup2(fd, 0);
        }
        else if (rdir == OUT_RDIR)
        {
            fd = open(rdirfilename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if (rdir == APPEND_RDIR)
        {
            fd = open(rdirfilename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
        }

        execvpe(argv[0], argv, environ);
        exit(EXIT_CODE);
    }
    else
    {
        int state = 0;
        pid_t ret = waitpid(id, &state, 0);
        if (ret == id)
        {
            lastcode = WEXITSTATUS(state);
            if (lastcode == EXIT_CODE)
                printf("command not found\n");
        }
        else
        {
            printf("%s\n", strerror(WEXITSTATUS(state)));
        }
    }
}

int bulidExecute(char* argv[], int argc)
{
    if (argc == 2 && strcmp(argv[0], "cd") == 0)
    {
        chdir(argv[1]);
        get_pwd();
        sprintf(getenv("PWD"), "%s", pwd);
        return 1;
    }
    else if (argc == 2 && strcmp(argv[0], "export") == 0)
    {
        strcpy(myenv, argv[1]);
        putenv(myenv);
        return 1;
    }
    else if (argc == 2 && strcmp(argv[0], "echo") == 0)
    {
        if (strcmp(argv[1], "$?") == 0)
            printf("%d\n", lastcode);
        else if (*argv[1] == '$')
        {
            char* val = getenv(argv[1] + 1);
            if (val)
                printf("%s\n", val);
        }
        else
            printf("%s\n", argv[1]);
        return 1;
    }
    if (strcmp(argv[0], "ls") == 0)
    {
        argv[argc++] = "--color";
        argv[argc] = NULL;
    }
    return 0;
}

int main()
{
    
    while (!quit)
    {
        rdir = NONE;
        rdirfilename = NULL;
        
        interact(command_line, sizeof(command_line) - 1);
        //printf("%s\n", command_line);
        int argc = split_string(command_line, argv);
        if (argc == 0)
            continue;
        //for (int i = 0; argv[i]; ++i)
        //    printf("[%d]: %s", i, argv[i]);
        
        int n = bulidExecute(argv, argc);

        if (!n)
            normal_execute(argv);
    }
    return 0;
}
